package org.sgx.gwteditors.client.impl1.complex;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.sgx.gwteditors.client.editor.Editor;
import org.sgx.gwteditors.client.editor.EditorNotFound;
import org.sgx.gwteditors.client.editor.Types;
import org.sgx.gwteditors.client.editor.error.EditorError;
import org.sgx.gwteditors.client.editor.error.EditorErrorManager;
import org.sgx.gwteditors.client.editor.event.EditorEventManager;
import org.sgx.gwteditors.client.editor.event.EditorValueChangeEvent;
import org.sgx.gwteditors.client.editor.event.ValueChangeListener;
import org.sgx.gwteditors.client.editor.props.PropertyHaver;
import org.sgx.gwteditors.client.impl1.EditorFramework1;
import org.sgx.gwteditors.client.impl1.EditorManager1;
import org.sgx.gwteditors.client.impl1.basetypes.Color;

import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;

/**
 * a simple propertyhaver editor in a flex table propName: propeEditor. this editor is complex this means that its properties can be other propertyhaver classes (BUT NOT IT SELF CLASS!)
 * IMPORTAN: for being edited in this editor, the propertyHaver must not have 
 * a property of its own (PropertyHaver) class.
 * 
 * this is a serius limitation, the user is responsible of avoiding bucles in the editor graph, this 
 * means editors must always end in simpleeditor leafs.
 * 
 * @author sg
 *
 * @param <T> the PropertyHaver class to edit
 */
public class PropertyHaverEditor1<T extends PropertyHaver> extends FlexTable 
	implements Editor<T>, ValueChangeListener<Object> {

	static Logger logger = Logger.getLogger(PropertyHaverEditor1.class+"");
	@Override
	public boolean isSimple() {
		return false;
	}
	
	private Map<String, Editor<?>> editors;
	private T model=null;

	public PropertyHaverEditor1() {
		super();
		editors=new HashMap<String, Editor<?>>();
	}
	
	@Override
	public void addValueChangeListener(ValueChangeListener<T> l) {
		EditorEventManager.<T>getInstance().addValueChangeListener(this, l);
	}
	@Override
	public List<EditorError<T>> getErrors() {
		return EditorErrorManager.<T>getInstance().getErrors(this);
	}
	@Override
	public void removeValueChangeListener(ValueChangeListener<T> l) {
		EditorEventManager.<T>getInstance().removeValueChangeListener(this, l);
	}

	@Override
	public T flush() {
		if(model==null) {		
			logger.log(Level.WARNING, "first load() must be called and then flush()");
			return null;
		}
		T obj = model; //we modify the passed PH !	
		boolean error=false;
//		Map<String, EditorError> errors = new HashMap<String, EditorError>();
		PropertyHaverEditorError<PropertyHaver> errors = new PropertyHaverEditorError(this);
		for(String propName : editors.keySet()) {
			Editor ed = editors.get(propName);
			if(ed!=null) {
				Object val = ed.flush();
				if(val==null && ed.getErrors().size()>0) {
					error=true;
//					List<EditorError> errors = ed.getErrors();
					errors.putFieldError(propName, ed.getErrors());
//					errors.put(key, value)
				}
				else {
					obj.propertySet(propName, val);
				}
//				logger.log(Level.WARNING, "flushed prop "+propName+" - "+val);
			}
		}
		if(error) {
			return null;
		}
		else {
			return obj;
		}
	}

	@Override
	public void load(T model) {
		this.model=model;
		clear();//clear gui
		editors.clear();//clear model
		removeMySelfAsListener();
		
		List<String> props = model.getProperties().keys();
		int i = 0;
		for(String name : props) {			
			Object val = model.propertyGet(name); 
			/* val can be null. this is no error since props can be null valued. */			

			String valClassName=val==null?"null":val.getClass().getName();
			if(model.getClass().getName().equals(valClassName)) {
				logger.log(Level.SEVERE, "property with same parent's propertyhaver type detected.");
				throw new RuntimeException("property with same parent's propertyhaver type detected.");
			}
				
			try {				
				Editor<Object> ed = (Editor<Object>) Types.getInstance().newPropertyEditor(model, name);
				
				ed.load(val);
				
				EditorEventManager.getInstance().addValueChangeListener(ed, this);
				
				editors.put(name, ed);
				
				Label label = new Label(name+":");
				setWidget(i, 0, label);
				
				Widget editorWidget = ed.getWidget();
				Widget widget = editorWidget;
				if(!ed.isSimple()) {
					DisclosurePanel panel = new DisclosurePanel(name);
					panel.add(editorWidget);
					widget=panel;
				}
				//add a listener to the child editor, and re fire each valuechange event but using this PropertyHaverEditor2 as a event source
				ed.addValueChangeListener(new ValueChangeListener<Object>() {
					@Override
					public void notifyValueChange(EditorValueChangeEvent<Object> evt) {
						EditorEventManager.<T>getInstance().fireValueChangeEvent(PropertyHaverEditor1.this); 
					}
				}); 
				setWidget(i, 1, widget);
				widget.setWidth("100%");				

				getCellFormatter().setVerticalAlignment(i, 1, HasVerticalAlignment.ALIGN_TOP);
				getCellFormatter().setVerticalAlignment(i, 0, HasVerticalAlignment.ALIGN_TOP);
			}catch (EditorNotFound e) {					
				// TODO: handle exception - editor not found for object
				logger.log(Level.WARNING, " exception building editor for prop "+name+" of class "+model.getClass()+" value: "+val+". Exception cause: "+e.getCause());
				e.printStackTrace();
				throw new RuntimeException(e);
			}
			i++;
		}		
	}

	@Override
	public Widget getWidget() {
		return this;
	}
	
	@Override
	public void notifyValueChange(EditorValueChangeEvent<Object> evt) {
		//TODO: here we do not flush the value to the event. Pass null, the user must explicitly flush this editor.
		//TODO: search whilch property is the editor evt.getSrc() and pass its name in a data attr in svt object
		EditorValueChangeEvent<T> e_ = new EditorValueChangeEvent<T>(this, null, evt);
		EditorEventManager.<T>getInstance().fireValueChangeEvent(this, e_);	
//		model= flush(); //update the model! user passed object modified!
	}
	
	
	private void removeMySelfAsListener() {
		for(Editor e : editors.values()) {
			EditorEventManager.getInstance().removeValueChangeListener(e, (ValueChangeListener<Object>) this);
		}
	}
	
	@Override
	public String getEditorId() {
		return EditorManager1.getInstance().getEditorName(this);		
	}

	@Override
	public String[] getEditorTypesSupported() {
		return new String[]{EditorFramework1.TYPE_PROPERTYHAVER}; 
	}
}
